/*******************************************************************************
#                                                                              #
#      MJPG-streamer allows to stream JPG frames from an input-plugin          #
#      to several output plugins                                               #
#                                                                              #
#      Copyright (C) 2007 Tom Stöveken                                         #
#                                                                              #
# This program is free software; you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License as published by         #
# the Free Software Foundation; version 2 of the License.                      #
#                                                                              #
# This program is distributed in the hope that it will be useful,              #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with this program; if not, write to the Free Software                  #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    #
#                                                                              #
*******************************************************************************/

/*
  This output plugin is based on code from output_file.c
  Writen by Dimitrios Zachariadis
  Version 0.1, May 2010

  It provides a mechanism to take snapshots with a trigger from a UDP packet.
  The UDP msg contains the path for the snapshot jpeg file
  It echoes the message received back to the sender, after taking the snapshot
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>

#include <dirent.h>

#include "../../utils.h"
#include "../../mjpg_streamer.h"

#define OUTPUT_PLUGIN_NAME "UDP output plugin"

// 定义指令类型
#define CMD_STREAM "get_stream"
#define CMD_PHOTO  "get_photo"


static pthread_t worker;
static globals *pglobal;
static int fd, delay, max_frame_size;
static char *folder = "/tmp/app";
static unsigned char *frame = NULL;
static char *command = NULL;
static int input_number = 0;

// UDP port
static int port = 0;
static struct sockaddr_in qt_client_addr; // 存储QT客户端地址
static int client_connected = 0;          // 标记是否有客户端连接

/******************************************************************************
Description.: print a help message
Input Value.: -
Return Value: -
******************************************************************************/
void help(void)
{
    fprintf(stderr, " ---------------------------------------------------------------\n" \
            " Help for output plugin..: "OUTPUT_PLUGIN_NAME"\n" \
            " ---------------------------------------------------------------\n" \
            " The following parameters can be passed to this plugin:\n\n" \
            " [-f | --folder ]........: folder to save pictures\n" \
            " [-d | --delay ].........: delay after saving pictures in ms\n" \
            " [-c | --command ].......: execute command after saveing picture\n" \
            " [-p | --port ]..........: UDP port to listen for picture requests. UDP message is the filename to save\n\n" \
            " [-i | --input ].......: read frames from the specified input plugin (first input plugin between the arguments is the 0th)\n\n" \
            " ---------------------------------------------------------------\n"
             " UDP指令格式:\n" \
            " get_stream - 开始视频流传输\n" \
            " get_photo [filename] - 捕捉并保存当前帧为图片\n" \
            " ---------------------------------------------------------------\n");
}

/******************************************************************************
Description.: clean up allocated resources
Input Value.: unused argument
Return Value: -
******************************************************************************/
void worker_cleanup(void *arg)
{
    static unsigned char first_run = 1;

    if(!first_run) {
        DBG("already cleaned up resources\n");
        return;
    }

    first_run = 0;
    OPRINT("cleaning up resources allocated by worker thread\n");

    if(frame != NULL) {
        free(frame);
    }
    close(fd);
}

/******************************************************************************
Description.: this is the main worker thread
              it loops forever, grabs a fresh frame and stores it to file
Input Value.:
Return Value:
******************************************************************************/
void *worker_thread(void *arg)
{
    int ok = 1, frame_size = 0, rc = 0;
    char buffer1[1024] = {0};
    unsigned char *tmp_framebuffer = NULL;
    int streaming = 0; // 标记是否处于视频流模式

    /* set cleanup handler to cleanup allocated resources */
    pthread_cleanup_push(worker_cleanup, NULL);

    // set UDP server data structures ---------------------------
    if(port <= 0) {
        OPRINT("a valid UDP port must be provided\n");
        return NULL;
    }
    struct sockaddr_in addr;
    int sd;
    int bytes;
    unsigned int addr_len = sizeof(addr);
    char udpbuffer[1024] = {0};
    sd = socket(PF_INET, SOCK_DGRAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    if(bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0)
        perror("bind");

    OPRINT("UDP服务器已启动,监听端口: %d\n", port);
    // -----------------------------------------------------------

    while(ok >= 0 && !pglobal->stop) {
        DBG("waiting for a UDP message\n");
        if(!streaming){
            // UDP receive ---------------------------------------------
            memset(udpbuffer, 0, sizeof(udpbuffer));
            bytes = recvfrom(sd, udpbuffer, sizeof(udpbuffer), 0, (struct sockaddr*)&addr, &addr_len);
            // --------------------------------------------------------- 
            // 保存客户端地址用于后续通信
            memcpy(&qt_client_addr, &addr, sizeof(addr));
            client_connected = 1;
            DBG("RECV OK:%s\n",udpbuffer);//收到udp指令

            // 处理指令
             if(strncmp(udpbuffer, CMD_STREAM, strlen(CMD_STREAM)) == 0) {
                streaming = 1;
                OPRINT("开始视频流传输\n");
                sendto(sd, "stream_started", 14, 0, (struct sockaddr*)&addr, sizeof(addr));
                continue;
            }else if(strncmp(udpbuffer, CMD_PHOTO, strlen(CMD_PHOTO)) == 0){
                char filename[256] = {0};
                // 提取文件名(如果有)
                if(strlen(udpbuffer) > strlen(CMD_PHOTO)+1) {
                    strcpy(filename, udpbuffer + strlen(CMD_PHOTO)+1);
                } else {
                    // 使用默认文件名
                    time_t now = time(NULL);
                    struct tm *t = localtime(&now);
                    snprintf(filename, sizeof(filename), "%s/snapshot_%04d%02d%02d_%02d%02d%02d.jpg",
                    folder, t->tm_year+1900, t->tm_mon+1, t->tm_mday,
                    t->tm_hour, t->tm_min, t->tm_sec);
                }
                DBG("准备捕捉照片: %s\n", filename);

                // 获取当前帧
                DBG("waiting for fresh frame\n");
                pthread_mutex_lock(&pglobal->in[input_number].db);
                pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);
                /* read buffer */
                frame_size = pglobal->in[input_number].size; 

                // 检查缓冲区大小
                
                if(frame_size > max_frame_size) {
                    DBG("increasing buffer size to %d\n", frame_size);

                    max_frame_size = frame_size + (1 << 16);
                    if((tmp_framebuffer = realloc(frame, max_frame_size)) == NULL) {
                        pthread_mutex_unlock(&pglobal->in[input_number].db);
                        LOG("not enough memory\n");
                        return NULL;
                    }

                    frame = tmp_framebuffer;
                }
                /* copy frame to our local buffer now */
                memcpy(frame, pglobal->in[input_number].buf, frame_size);

                /* allow others to access the global buffer again */
                pthread_mutex_unlock(&pglobal->in[input_number].db);

                // 保存照片
                if((fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
                    OPRINT("无法打开文件 %s\n", filename);
                    sendto(sd, "error: cannot open file", 22, 0, (struct sockaddr*)&qt_client_addr, sizeof(qt_client_addr));
                    continue;
                }
                
                if(write(fd, frame, frame_size) < 0) {
                    OPRINT("写入文件失败 %s\n", filename);
                    perror("write()");
                    close(fd);
                    sendto(sd, "error: write failed", 19, 0, (struct sockaddr*)&qt_client_addr, sizeof(qt_client_addr));
                    continue;
                }
                
                close(fd);
                OPRINT("照片已保存: %s\n", filename);
                sendto(sd, filename, strlen(filename), 0, (struct sockaddr*)&qt_client_addr, sizeof(qt_client_addr));
                
                // 执行后续命令(如果有)
                if(command != NULL) {
                    memset(buffer1, 0, sizeof(buffer1));
                    snprintf(buffer1, sizeof(buffer1), "%s \"%s\"", command, filename);
                    DBG("执行命令: %s", buffer1);
                    
                    if((rc = setenv("MJPG_FILE", filename, 1)) != 0) {
                        LOG("setenv失败 (返回值 %d)\n", rc);
                    }
                    
                    if((rc = system(buffer1)) != 0) {
                        LOG("命令执行失败 (返回值 %d)\n", rc);
                    }
                }
                
                // 延迟(如果有)
                if(delay > 0) {
                    usleep(1000 * delay);
                }
            }
        } 
        else { // 流模式
            // 检查是否有停止流指令
            fd_set readfds;
            struct timeval tv;
            
            FD_ZERO(&readfds);
            FD_SET(sd, &readfds);
            tv.tv_sec = 0;
            tv.tv_usec = 1000; // 1ms超时
            
            if(select(sd+1, &readfds, NULL, NULL, &tv) > 0) {
                memset(udpbuffer, 0, sizeof(udpbuffer));
                bytes = recvfrom(sd, udpbuffer, sizeof(udpbuffer), 0, (struct sockaddr*)&addr, &addr_len);
                
                if(strncmp(udpbuffer, "stop_stream", 11) == 0) {
                    streaming = 0;
                    OPRINT("停止视频流传输\n");
                    sendto(sd, "stream_stopped", 14, 0, (struct sockaddr*)&qt_client_addr, sizeof(qt_client_addr));
                    continue;
                }
            }
             // 获取当前帧
            pthread_mutex_lock(&pglobal->in[input_number].db);
            pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);
            
            frame_size = pglobal->in[input_number].size;
            
            // 检查缓冲区大小
            if(frame_size > max_frame_size) {
                DBG("增加缓冲区大小到 %d\n", frame_size);
                max_frame_size = frame_size + (1 << 16);
                if((tmp_framebuffer = realloc(frame, max_frame_size)) == NULL) {
                    pthread_mutex_unlock(&pglobal->in[input_number].db);
                    LOG("内存不足\n");
                    return NULL;
                }
                frame = tmp_framebuffer;
            }
            
            memcpy(frame, pglobal->in[input_number].buf, frame_size);
            pthread_mutex_unlock(&pglobal->in[input_number].db);
            
            // 发送帧到客户端
            if(client_connected) {
                if(sendto(sd, frame, frame_size, 0, (struct sockaddr*)&qt_client_addr, sizeof(qt_client_addr)) < 0) {
                    perror("发送帧数据失败");
                    client_connected = 0;
                }
            }
            
            // 控制帧率(约30fps)
            usleep(1000000 / 30);
        }

    }
    // 关闭UDP端口
    if(port > 0)
        close(sd);

    /* 清理资源 */
    pthread_cleanup_pop(1);

    return NULL;
}    


        


/*** plugin interface functions ***/
/******************************************************************************
Description.: this function is called first, in order to initialise
              this plugin and pass a parameter string
Input Value.: parameters
Return Value: 0 if everything is ok, non-zero otherwise
******************************************************************************/
int output_init(output_parameter *param)
{
    int i;

    delay = 0;

    param->argv[0] = OUTPUT_PLUGIN_NAME;

    /* show all parameters for DBG purposes */
    for(i = 0; i < param->argc; i++) {
        DBG("argv[%d]=%s\n", i, param->argv[i]);
    }

    reset_getopt();
    while(1) {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0
            },
            {"help", no_argument, 0, 0},
            {"f", required_argument, 0, 0},
            {"folder", required_argument, 0, 0},
            {"d", required_argument, 0, 0},
            {"delay", required_argument, 0, 0},
            {"c", required_argument, 0, 0},
            {"command", required_argument, 0, 0},
            {"p", required_argument, 0, 0},
            {"port", required_argument, 0, 0},
            {"i", required_argument, 0, 0},
            {"input", required_argument, 0, 0},
            {0, 0, 0, 0}
        };

        c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);

        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help();
            return 1;
        }

        switch(option_index) {
            /* h, help */
        case 0:
        case 1:
            DBG("case 0,1\n");
            help();
            return 1;
            break;

            /* f, folder */
        case 2:
        case 3:
            DBG("case 2,3\n");
            folder = malloc(strlen(optarg) + 1);
            strcpy(folder, optarg);
            if(folder[strlen(folder)-1] == '/')
                folder[strlen(folder)-1] = '\0';
            break;

            /* d, delay */
        case 4:
        case 5:
            DBG("case 4,5\n");
            delay = atoi(optarg);
            break;

            /* c, command */
        case 6:
        case 7:
            DBG("case 6,7\n");
            command = strdup(optarg);
            break;
            /* p, port */
        case 8:
        case 9:
            DBG("case 8,9\n");
            port = atoi(optarg);
            break;
            /* i, input */
        case 10:
        case 11:
            DBG("case 10,11\n");
            input_number = atoi(optarg);
            break;
        }
    }

    pglobal = param->global;
    if(!(input_number < pglobal->incnt)) {
        OPRINT("ERROR: the %d input_plugin number is too much only %d plugins loaded\n", input_number, pglobal->incnt);
        return 1;
    }
    OPRINT("input plugin.....: %d: %s\n", input_number, pglobal->in[input_number].plugin);
    OPRINT("output folder.....: %s\n", folder);
    OPRINT("delay after save..: %d\n", delay);
    OPRINT("command...........: %s\n", (command == NULL) ? "disabled" : command);
    if(port > 0) {
        OPRINT("UDP port..........: %d\n", port);
    } else {
        OPRINT("UDP port..........: %s\n", "disabled");
    }
    return 0;
}

/******************************************************************************
Description.: calling this function stops the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_stop(int id)
{
    DBG("will cancel worker thread\n");
    pthread_cancel(worker);
    return 0;
}

/******************************************************************************
Description.: calling this function creates and starts the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_run(int id)
{
    DBG("launching worker thread\n");
    pthread_create(&worker, 0, worker_thread, NULL);
    pthread_detach(worker);
    return 0;
}
